package com.salt.core.log.aspect;

import com.auth0.jwt.JWT;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.salt.common.utils.DateUtil;
import com.salt.core.log.annotation.OperationRecord;
import com.salt.core.log.prop.KafkaProperties;
import com.salt.core.log.sender.KafkaSender;
import com.salt.core.log.utils.LogUtil;
import com.salt.modules.log.domain.OperationLog;
import com.salt.modules.log.enums.BusinessStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @Description
 * @Author salt
 * @Date 2023/4/17
 * @Version 1.0.1
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    /** 排除敏感属性字段 */
    public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword", "confirmNewPassword"};
    /** 计算操作消耗时间 */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    @Resource
    private KafkaSender kafkaSender;
    @Resource
    private KafkaProperties kafkaProperties;

    @Pointcut("@annotation(com.salt.core.log.annotation.OperationRecord)")
    public void logPoint() {}

    @Before("logPoint()")
    public void before(JoinPoint joinPoint) {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    @AfterReturning(pointcut = "logPoint()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        handleLog(joinPoint, null, result);
    }

    /**
     * 拦截异常操作
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(pointcut = "logPoint()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }


    /*@Around("logPoint()")
    public Object around(ProceedingJoinPoint point) {

        Object proceed = null;
        try {
            ServletRequestAttributes attributes =
                    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes == null)
                return null;
            HttpServletRequest request = attributes.getRequest();

            OperationLog operationLog = new OperationLog();
            Signature signature = point.getSignature();
            if (signature instanceof MethodSignature) {
                Class<?> targetClass = point.getTarget().getClass();
                MethodSignature ms = (MethodSignature) signature;
                Method targetMethod = targetClass.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
                OperationRecord operationRecord = targetMethod.getAnnotation(OperationRecord.class);
                if (operationRecord != null) {
                    operationLog.setService(operationRecord.service());
                    operationLog.setModule(operationRecord.module());
                    operationLog.setOperation(operationRecord.operation());
                    operationLog.setDescription(operationRecord.description());
                }
            }

            List<String> excludeParams = Arrays.asList(EXCLUDE_PROPERTIES);

            List<String> params = new ArrayList<>();
            Enumeration<String> enumeration = request.getParameterNames();
            if (enumeration.hasMoreElements()) {
                while (enumeration.hasMoreElements()) {
                    String paramName = enumeration.nextElement();
                    if (!excludeParams.contains(paramName)){
                        params.add(paramName + ":" + request.getParameter(paramName));
                    }
                }
            } else {
                Object[] args = point.getArgs();
                for (Object arg:args) {
                    if (!excludeParams.contains(arg.toString())){
                        params.add(arg.toString());
                    }
                }
            }
            operationLog.setParams(params);
            //operationLog.setId();
            operationLog.setRequestMethod(request.getMethod());
            operationLog.setOperUrl(request.getRequestURI());
            operationLog.setOperIp(request.getRemoteAddr());
            operationLog.setOperTime(DateUtil.getDateTime());
            long startTime = System.currentTimeMillis();
            proceed = point.proceed();
            operationLog.setExeTime(System.currentTimeMillis() - startTime);
            operationLog.setResult(proceed);
            User user = userClient.getUser();
            if (user != null) {
                operationLog.setOperUserId(user.getId());
                operationLog.setOperUserName(user.getName());
            }
            kafkaSender.send(operationLog);
        } catch (Throwable throwable) {
            log.error("处理日志异常信息:{}", throwable.getMessage());
        }
        return proceed;
    }*/

    private void handleLog(final JoinPoint point, final Exception e, Object result) {
        if (!kafkaProperties.getOperEnable())
            return;
        try {
            ServletRequestAttributes attributes =
                    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes == null)
                return;
            HttpServletRequest request = attributes.getRequest();
            String token = request.getHeader("token");

            OperationLog operationLog = new OperationLog();
            Signature signature = point.getSignature();
            if (signature instanceof MethodSignature) {
                Class<?> targetClass = point.getTarget().getClass();
                MethodSignature ms = (MethodSignature) signature;
                Method targetMethod = targetClass.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
                OperationRecord operationRecord = targetMethod.getAnnotation(OperationRecord.class);
                if (operationRecord != null) {
                    operationLog.setService(operationRecord.service());
                    operationLog.setModule(operationRecord.module());
                    operationLog.setOperation(operationRecord.operation());
                    operationLog.setDescription(operationRecord.description());
                }
            }
            List<String> excludeParams = Arrays.asList(EXCLUDE_PROPERTIES);

            List<String> params = new ArrayList<>();
            Enumeration<String> enumeration = request.getParameterNames();
            if (enumeration.hasMoreElements()) {
                while (enumeration.hasMoreElements()) {
                    String paramName = enumeration.nextElement();
                    if (!excludeParams.contains(paramName)){
                        params.add(paramName + ":" + request.getParameter(paramName));
                    }
                }
            } else {
                Object[] args = point.getArgs();
                for (Object arg:args) {
                    if (!excludeParams.contains(arg.toString())){
                        params.add(arg.toString());
                    }
                }
            }

            operationLog.setParams(params);
            operationLog.setStatus(BusinessStatus.SUCCESS.ordinal());
            if (e != null) {
                operationLog.setStatus(BusinessStatus.FAIL.ordinal());
                operationLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            operationLog.setRequestMethod(request.getMethod());
            operationLog.setOperUrl(request.getRequestURI());
            operationLog.setOperIp(LogUtil.getIpAddress(request));
            operationLog.setOperTime(DateUtil.getDateTime());
            operationLog.setExeTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
            operationLog.setResult(StringUtils.substring(result.toString(), 0, 2000));
            Map<String, String> userInfo = tokenUserInfo(token);
            operationLog.setUserName(userInfo.get("username"));
            operationLog.setOperRealName(userInfo.get("realname"));
            operationLog.setOperUserId(userInfo.get("userid"));
            kafkaSender.send(operationLog);
        } catch (Exception exp) {
            log.error("处理日志异常信息:{}", exp.getMessage());
        } finally {
            TIME_THREADLOCAL.remove();
        }
    }

    public Map<String, String> tokenUserInfo(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            Map<String, String> claims = new HashMap<>();
            claims.put("userid", jwt.getClaim("userid").asString());
            claims.put("username", jwt.getClaim("username").asString());
            claims.put("realname", jwt.getClaim("realname").asString());
            return claims;
        } catch (JWTDecodeException e) {
            return null;
        }
    }




}
